home *** CD-ROM | disk | FTP | other *** search
/ Aminet 2 / Aminet AMIGA CDROM (1994)(Walnut Creek)[Feb 1994][W.O. 44790-1].iso / Aminet / gfx / 3d / rotdemo.lha / rot.description < prev    next >
Text File  |  1993-01-09  |  14KB  |  268 lines

  1.  
  2. 3DGame Engine, by: Jason Freund and Gabe Dalbec.
  3.  
  4.  
  5.  
  6.  
  7.                                 INTRO
  8.  
  9.     This paper describes the algorithm used to create the demo, the current
  10. problems with it, and possiblities for future expansion.  It's intended for
  11. people who might want to join a team to develop this into a PD game (see
  12. rot.invite file) or people curious about the technical aspects of the
  13. program.
  14.  
  15.  
  16.  
  17.                              CURRENT WORK
  18.  
  19.     I haven't put much time into this program lately because my partner is
  20. in Mexico for a couple of months and because of school.  But there are two
  21. issues I'm trying to fix now.
  22.  
  23.     First, the perspective problem, described below.  The first thing you
  24. notice about the demo is that walls get stretched.
  25.  
  26.     The second problem is with using the 5th bitplane.  Right now, the
  27. v-flipping the top half of the screen to create the bottom is done with the
  28. copper.  First, I need to rewrite the fast-to-chip routine to draw the
  29. bottom half of the screen.  This will slow down the program a little, but
  30. it's necessary if I want objects on the floor (and I do).  I also need to
  31. modify this fast-to-chip routine to draw into the fifth bitplane instead of
  32. allowing the blitter to do it (as described below).  The fifth bitplane is
  33. also a nuisance with my custom brush routines.  My partner wrote  his own
  34. brush compression and optimized assembly custom blitter routines so that
  35. graphics are compressed in memory.  My fake fifth bitplane technique isn't
  36. compatible with this.  So before I can display objects into the lower 16
  37. color palette, I need to modify the blitting routines.
  38.  
  39.  
  40.  
  41.  
  42.                         OVERVIEW OF THE ALGORITHM
  43.  
  44.     This program doesn't use any texture mapping or ray tracing. Everything
  45. is faked.  There are many limitations on the engine which allow us to fake
  46. things. First, walls are all equal-length and equal height. They can only be
  47. spaced multiples of 1 wall length apart.  You cannot see through walls.
  48. Walls must be perpendicular to each other.  The whole dungeon is vertically
  49. symmetrical.  These assumptions allow us to generate each frame in "screen"
  50. space -- by looking at the start and endpoints of the tops of each wall face.
  51.  
  52.     To reduce the number of rotations per frame, the dungeon data is in
  53. third normal form.  That means we have a list of (X,Y,Z) points representing
  54. endpoints of wall faces and a list of edges that reference this list of
  55. points.  We also create a list of midpoints from the list of edges.   The Z
  56. values of each point (or height of the walls) are a +constant (ie 0.6).
  57. This way each edge represents the top line of a wall. All you need is this
  58. top edge to compute the "zbuffer".
  59.  
  60.     Each frame is defined by a 1 line "z-buffer": typedef struct {
  61.     short height;
  62.     short bitmap;
  63.     short offset;
  64.     short junk;   /* to make structure 2^3 long */ } mapline; mapline
  65. zbuffer[SCREENWIDTH];
  66.  
  67. Now, for each 1 pixel column on the screen, you have:
  68.  
  69. 1) height == how far from the top of the screen to start drawing the column.
  70. You draw from height, down to the center of the screen. 2) bitmap == which
  71. source bitmap number to draw from at that column. 3) offset == How far in
  72. the source bitmap to go to grab the column to draw.
  73.  
  74.     For each frame you generate a new "zbuffer" by examining every point
  75. between the start and endpoint of every edge (that you can at least
  76. partially see) in the dungeon.  First, set height=MIDDLEOFSCREEN for every
  77. column in the zbuffer.  I use integer Breshenham's to generate every point
  78. between the endpoints of the line segment.  Then for each point on the line,
  79. check to see if the Y coord is higher than the corresponding
  80. zbuffer[i].height.  If it is, then that point represents a column that will
  81. be in front of everything else and you should update every field in
  82. zbuffer[i].  When you've done this for every edge, your zbuffer will contain
  83. everything you need to describe the scene.
  84.  
  85.     Once you generate the zbuffer, just loop through the array, yank _column
  86. from _bitmap and draw it from _height.  Since you know how high the wall is,
  87. you can calculate how much to shrink or expand that source bitmap column and
  88. skip or redraw rows of the column right in the loop that copies it to the
  89. screen.
  90.  
  91.  
  92. COPY-OUT
  93.  
  94.     The "copy-out" routine is the biggest bottleneck in the program.  The
  95. "copy-out" routine copies a column from the source bitmap to the screen,
  96. shrinking or expanding it as it copies.  I will spend some time writing
  97. about this aspect of the program since it is the most important part.
  98.     To begin with, it is the most optimized assembly we've ever written. It
  99. uses lookup tables for every calculation where using a lookup table will
  100. save a cycle. This is the routine where we made huge changes to our code to
  101. implement schemes that would remove 1 cycle from the innermost loop.  We
  102. tried about a half dozen schemes before we came up with our final version.
  103.  
  104.     First, our bitmaps are preconverted to 1 big chunky plane rather than 4
  105. small bitplanes; the first ulong represents the first 8 pixels.
  106. Consequently, when you want to display a pixel, you do shifts, ands, and ors
  107. a longword at a time and get the bits for all 4 bitplanes at once. These
  108. calculations are all done in FAST memory and rendered onto a FAST memory
  109. screen-sized area. Then, when you're all done rendering the screen, you copy
  110. it all to planar-mode CHIP memory so you can see it.
  111.  
  112.     It's convenient that the 3000's memory and bus are all four bytes wide
  113. because that gives us all the colors we could ask for: 16.  But we're
  114. getting 32 colors for almost free.  The only cost is blit-clearing a fifth
  115. bitplane (half the size of the screen), 20% bus contention with denise chip
  116. which is in competition for bus time to display the picture, and expanding
  117. our copy-out loop by one to write a 1 or 0 into the fifth plane according to
  118. which palette is being used in that column.  It turns out that all these
  119. costs don't add up to more than about 5% slowdown.  So we took it. The
  120. "source bitmap number" field of our mapline is also an index into an array
  121. that tells us which palette (0 or 1) the bitmap uses. Our "copy-out" routine
  122. sets the appropriate bit in the fifth bitplane of the destination screen
  123. based on this lookup without taking any time away from the longword logical
  124. operations on the source bitmap.  We used to employ the blitter to fill the
  125. fifth bitplane in one fell swoop instead of filling it byte-by-byte withe
  126. the CPU.  This was done by drawing vertical lines everywhere the scene
  127. changed palette.  Then we'd fill the screen, telling the blitter to change
  128. fill modes every time it hit a line.  Not only was this slower, but we got a
  129. slight flickering when we drew the fifth bitplane with blitlines and a
  130. blitfill due to the fact that we were drawing to the screen at 2 different
  131. times.  But when we started to fill the fifth bitplane inside the copy-out
  132. loop, the flickering was fixed.
  133.  
  134.     One of the copy-out methods we experimented with was a pipelined engine.
  135. I am describing it because it would be useful for people who want to modify
  136. our engine to run on the 500 which doesn't have a fast CPU.  You can use the
  137. CPU to generate the zbuffer while the blitter clears the screen.  Then the
  138. CPU would perform the copy-out.  Then the Blitter would do the Vflip while
  139. the CPU rotated the points for the next frame, and so on.  We rejected this
  140. and other schemes because a 68030 CPU is faster than the blitter for
  141. everything; any parallelism would not speed up the pipeline.  In fact, after
  142. everything was optimized for the CPU, we didn't need the blitter at all.
  143.  
  144.  
  145.  
  146.                                PROBLEMS:
  147.  
  148.     One problem we found is that at the corners of the walls, a few pixels
  149. from a wall behind would overlap the wall in front. This ocurred because
  150. we're not using any painter's algorithm to traverse the list of edges.  At
  151. the corners where both walls at a corner are at the same height, there is no
  152. way to judge which is actually in front.  Another problem we had is that
  153. corners didn't exactly line up because we were using breshenhams to
  154. calculate points along a line segment.
  155.  
  156.     Both of these problems were fixed by multiplying every Y value by 32
  157. before calculating points between the endpoints of the edge and then
  158. dividing by 32 after the zbuffer is generated.  Be sure to check for -Y
  159. values before lsl/r #5's to preserve the sign of Y.
  160.  
  161.     Another problem that we still have is with the perspective of the walls.
  162. Flat walls or walls far away are fine.   But walls that are close to you get
  163. smeared. This is because we don't calculate any real perspective.  We just
  164. use a linear mapping based on the ratio of the width of the rotated wall to
  165. the width of the flat, original source bitmap to calculate offsets to the
  166. source bitmap.   Walls that are at a steep angle and close to you end up
  167. drawing from a very small portion from the source bitmap.
  168.  
  169.     One solution that we tried was to generate real perspective based on a
  170. recursive algorithm.  We already have a list of midpoints for each wall face
  171. that we use to detect wall collisions.  If we rotate these midpoints, we can
  172. find the ratio between the width on the screen of the close half of the wall
  173. to the width of the far half of the wall.  This midpoint will draw from the
  174. center of the source bitmap.  Then we just recursively subdide each half of
  175. the wall, keeping track of the new corresponding center on the source bitmap
  176. as we go, building an array of source bitmap offsets.  This is slow, but
  177. could be precomputed and simulated with a lookup table that just takes in a
  178. ratio to lookup offsets for the largest possible wall. Smaller walls could
  179. then use a linear mapping from this large wall. Unfortunately, we still
  180. haven't been able to get the recursive routine to terminate correctly.
  181.  
  182.  
  183.  
  184.  
  185.                                SPEEDUPS:
  186.  
  187.     Our first speedup was to only draw the top half of the screen and use
  188. the copper to reflect the top half onto the bottom half. You can also get a
  189. cute floor and ceiling for free out of this by changing the background
  190. colors at certain rows.  This speedup makes the program a good 35% faster
  191. than drawing all the way down. If this is ever turned into a game with
  192. objects and monsters, this will obviously have to be re-done with the CPU
  193. (or blitter for the 500) because vertically symmetrical monsters would be a
  194. little annoying. But even without using the copper, this speedup should be
  195. well worth cost of losing non-symmetrical walls.
  196.  
  197.     The list of (X,Y) coordinates representing the endpoints of wall faces
  198. is sorted by X coordinate.  Wall collisions are handled by checking every
  199. point in the maze.  If any point is inside the box you are about to move
  200. into, an X and/or Y component of your movement is set to 0 so that you slide
  201. along the edge of a wall. To make the check against every point fast for
  202. large dungeons, we sort the list of points by X coordinate and only check
  203. the sublist until the X coordinate is out of the range of your box.
  204.  
  205.  
  206.  
  207.  
  208.                       AREAS FOR FUTURE EXPANSION
  209.  
  210. 2 STORY BUILDINGS
  211.  
  212.     All you need to do is keep a second list of edges representing wall
  213. faces on the second story.  The only difference is that you have to build
  214. the FAST mem off-screen in two stages.  First build a mapline and copy-out
  215. the second story. Then build a mapline and copy-out the first story.  Then
  216. copy the whole thing to CHIP memory, as usual.  You probably don't want too
  217. many 2 story buildings -- so the first stage should be very quick compared
  218. to the second.
  219.  
  220. WINDOWS
  221.  
  222.     Windows would be somewhat difficult to implement in our engine.  If it
  223. were possible to see through a window, through a window, through a window,
  224. then you would need 4 zbuffers. Painter's algorithm is not necessary.  As
  225. you build your zbuffer, when you find a wall closer than what was previously
  226. in the zbuffer for that column, instead of just overwriting what was
  227. previously in there with the new value, you simply shift every zbuffer down
  228. one and then write the new value in the top-most zbuffer.  Without using any
  229. extra time, you now have zbuffers for every depth and can basically draw the
  230. screen left-to-right all at once.
  231.  
  232. DOORS:
  233.  
  234.     Doors would be easy as long as they scroll horizontally as they open so
  235. that you still only need 1 zbuffer.  Anything fancier than plain
  236. horizontally scrolling doors would enable you to see more than one wall at a
  237. given column which is not possible.
  238.  
  239. OBJECTS/MONSTERS:
  240.  
  241.     This would also be very easy.  Have a second set of (X,Y) points that
  242. represent objects.  Just blit objects onto the screen after you've finished
  243. everything else.  The zbuffer can easily tell you how much of an object is
  244. obscured by a wall so that you can clip the appropriate amount when you blit.
  245.  
  246. A500 COMPATABILITY:
  247.  
  248.     This program was not meant for machines without a fast CPU or a math
  249. chip. We don't have access to a 500 and we have no particular desire to see
  250. it run fast on one.  We'd be happy to turn our sourcecode over to anyone who
  251. can read C and can program real well on the 500.
  252.  
  253.     Some things that would have to be done are:
  254.  
  255. 1) Shrink the screensize.  I think one-quarter screen would be small enough.
  256. Our engine code is modular enough to make adjusting the screen size trivial.
  257. 2) Rewrite several routines to use integer math.  I'm sure eurodemo coders
  258. have a lot of experience there.  Integer math may even help the A3000
  259. version. 3) Vflip screen with blitter instead of CPU.  Use parallel
  260. processing where-ever possible (see COPY-OUT, above). 4) The two 16 color
  261. palette trick is done with the CPU.  It would have to be redone using
  262. vertical lines and 1 blitter fill.  Actually, this is the way we originally
  263. did it, so the code is already there -- just commented out.  Of course,
  264. using the blitter, as explained in COPY-OUT, above, will cause flickering
  265. because you are drawing once for the top four plains and then again later
  266. for the fifth plane.  This could be fixed, however, with doublebuffering
  267. which you'll probably need anyway on the 500.
  268.